import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision.models import resnet50


class ResNet50(nn.Module):
    def __init__(self, pretrained=False):
        super(ResNet50, self).__init__()
        self.in_channels = [256, 512, 1024, 2048]
        self.res = resnet50(pretrained=pretrained)
        self.layer0 = nn.Sequential(
            self.res.conv1,
            self.res.bn1,
            self.res.relu
        )
        self.maxpool = self.res.maxpool
        self.layer1 = self.res.layer1
        self.layer2 = self.res.layer2
        self.layer3 = self.res.layer3
        self.layer4 = self.res.layer4

    def forward(self, x):
        x0 = self.layer0(x)
        x1 = self.layer1(self.maxpool(x0))
        x2 = self.layer2(x1)
        x3 = self.layer3(x2)
        x4 = self.layer4(x3)
        return [x1, x2, x3, x4]


class ConvBNReLU(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, bias=False):
        super(ConvBNReLU, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, dilation, bias=bias)
        self.bn = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(True)

        self.init_weight()

    def init_weight(self):
        for layer in self.modules():
            if isinstance(layer, nn.Conv2d):
                nn.init.kaiming_normal_(layer.weight, a=1)
                if layer.bias is not None:
                    nn.init.constant_(layer.bias, 0)
            if isinstance(layer, nn.BatchNorm2d):
                nn.init.constant_(layer.weight, 1)
                if layer.bias is not None:
                    nn.init.constant_(layer.bias, 0)

    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x


class UpConv1(nn.Module):
    def __init__(self, in_channels):
        super(UpConv1, self).__init__()
        self.up = nn.Sequential(
            ConvBNReLU(in_channels, in_channels, 3, 1, 1)
        )
        self.conv = nn.Sequential(
            ConvBNReLU(in_channels * 2, in_channels // 2, 3, 1, 1)
        )

    def forward(self, de, en):
        de = F.interpolate(de, en.size()[2:], mode="bilinear", align_corners=False)
        de = self.up(de)
        out = self.conv(torch.cat([de, en], dim=1))
        return out


class Generator(nn.Module):
    def __init__(self, num_classes, pretrained=None):
        super(Generator, self).__init__()
        self.backbone = ResNet50(pretrained)
        self.middle = ConvBNReLU(self.backbone.in_channels[3], self.backbone.in_channels[2], 3, 1, 1)
        self.up3 = UpConv1(self.backbone.in_channels[2])
        self.up2 = UpConv1(self.backbone.in_channels[1])
        self.up1 = UpConv1(self.backbone.in_channels[0])
        self.out = nn.Conv2d(self.backbone.in_channels[0] // 2, num_classes, 1)

    def forward(self, xx):
        x = self.backbone(xx)
        out = self.middle(x[3])
        out = self.up3(out, x[2])
        out = self.up2(out, x[1])
        out = self.up1(out, x[0])
        out = self.out(out)
        out = F.interpolate(out, xx.size()[2:], mode='bilinear', align_corners=False)
        return out
